﻿using Silk.NET.Maths;
using Silk.NET.Windowing;
using Silk.NET.Windowing.Extensions;
using Silk.NET.Input;
using Silk.NET.Input.Glfw;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using GLES = Silk.NET.OpenGLES;


namespace OpenGLLib
{
    public sealed unsafe class SilkOpenGLESUtils
    {
        private IWindow win;
        private IInputContext inputCtx;
        private GLES.GL gl;
        private uint width;
        private uint height;
        private uint shaderProgram;
        private int ySamplerLoc;
        private int uvSamplerLoc;
        private uint yTextureId;
        private uint uvTextureId;

        public void CreateWin(uint width = 0u, uint height = 0u, string title = null)
        {
            width = this.width = width < 1 ? 1280u : width;
            height = this.height = height < 1 ? 720u : height;
            win = SilkOpenGLESUtilsExtensions.CreateNativeWin((int)this.width, (int)this.height, title);
            if (win != null)
            {
                win.Load += OnLoad;
                win.Update += OnUpdate;
                win.Render += OnRender;

                win.Initialize();
                gl = GLES.GL.GetApi(win);
                InitGLES(gl);

                //InitShaderProgram(out shaderProgram, out ySamplerLoc, out uvSamplerLoc);
                yTextureId = gl.GenTexture();
                uvTextureId = gl.GenTexture();
            }
        }

        private void OnLoad()
        {
            inputCtx = win.CreateInput();
            for (int i = 0; i < inputCtx.Keyboards.Count; i++)
            {
                inputCtx.Keyboards[i].KeyDown += KeyDown;
            }
        }

        private void KeyDown(IKeyboard arg1, Key arg2, int arg3)
        {
            if (arg2 == Key.Escape)
            {
                win.Close();
            }
        }

        public void Show() => win.Run();

        public void Close() => win.Close();

        public void InitShaderProgram(out uint shaderProgram, out int ySamplerLoc, out int uvSamplerLoc)
        {
            uvSamplerLoc = ySamplerLoc = 0;
            shaderProgram = 0;

            string vShaderStr = @"
#version 300 es
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec2 a_texCoord;
out vec2 v_texCoord;
void main()
{
gl_Position = a_position;
v_texCoord = a_texCoord;
}";
            string fShaderStr = @"
#version 300 es
precision mediump float;
in vec2 v_texCoord;                            
layout(location = 0) out vec4 outColor;
uniform sampler2D y_texture;
uniform sampler2D uv_texture;
void main()
{                                         
vec3 yuv;
yuv.x = texture(y_texture, v_texCoord).r;
yuv.y = texture(uv_texture, v_texCoord).a-0.5;
yuv.z = texture(uv_texture, v_texCoord).r-0.5;
vec3 rgb =mat3( 1.0,       1.0,           1.0,
                0.0,         -0.344,     1.770,
                1.403,  -0.714,       0.0) * yuv;
outColor = vec4(rgb, 1);
}";
            //string y_texture = "y_texture";
            //string uv_texture = "uv_texture";
            //IntPtr yPtr = Marshal.StringToHGlobalAnsi(y_texture);
            //IntPtr uvPtr = Marshal.StringToHGlobalAnsi(uv_texture);
            //shaderProgram = gl.CreateShaderProgram(GLES.GLEnum.Shader, 2, new string[] { vShaderStr, fShaderStr });
            //ySamplerLoc = gl.GetUniformLocation(shaderProgram, (byte*)(yPtr.ToPointer()));
            //uvSamplerLoc = gl.GetUniformLocation(shaderProgram, (byte*)(uvPtr.ToPointer()));

            uint mainId = gl.CreateProgram();
            uint vertex = gl.CreateShader(GLES.GLEnum.VertexShader);
            gl.ShaderSource(vertex, vShaderStr);
            gl.AttachShader(mainId, vertex);

            uint fragment = gl.CreateShader(GLES.GLEnum.FragmentShader);
            gl.ShaderSource(fragment, fShaderStr);
            gl.AttachShader(mainId, fragment);

            gl.LinkProgram(mainId);
            gl.GetProgram(mainId, GLES.GLEnum.LinkStatus, out int success);
            if (success < 1)
            {
                string log = gl.GetProgramInfoLog(mainId);
                throw new Exception(log);
            }
            gl.DeleteShader(vertex);
            gl.DeleteShader(fragment);
        }

        public unsafe void UpdateYuv(byte*[] ptrs, int[] lines) // int width = 0, int height = 0
        {
            if (shaderProgram == 0 || ySamplerLoc == 0 || uvSamplerLoc == 0)
            {
                return;
            }
            byte* yAddr = ptrs[0];
            byte* uAddr = ptrs[1];
            byte* vAddr = ptrs[2];

            //upload Y plane data
            gl.BindTexture(GLES.GLEnum.Texture2D, yTextureId);
            gl.TexImage2D(GLES.GLEnum.Texture2D, 0, (int)GLES.GLEnum.Luminance, width, height,
                0, GLES.GLEnum.Luminance, GLES.GLEnum.UnsignedByte, yAddr);
            gl.TexParameter(GLES.GLEnum.Texture2D, GLES.GLEnum.TextureWrapS, (int)GLES.GLEnum.ClampToEdge);
            gl.TexParameter(GLES.GLEnum.Texture2D, GLES.GLEnum.TextureWrapT, (int)GLES.GLEnum.ClampToEdge);
            gl.TexParameter(GLES.GLEnum.Texture2D, GLES.GLEnum.TextureMinFilter, (int)GLES.GLEnum.Linear);
            gl.TexParameter(GLES.GLEnum.Texture2D, GLES.GLEnum.TextureMagFilter, (int)GLES.GLEnum.Linear);
            gl.BindTexture(GLES.GLEnum.Texture2D, (int)GLES.GLEnum.None);

            //upload UV plane data
            gl.BindTexture(GLES.GLEnum.Texture2D, uvTextureId);
            gl.TexImage2D(GLES.GLEnum.Texture2D, 0, (int)GLES.GLEnum.LuminanceAlpha, width, height,
                0, GLES.GLEnum.LuminanceAlpha, GLES.GLEnum.UnsignedByte, uAddr);
            gl.TexParameter(GLES.GLEnum.Texture2D, GLES.GLEnum.TextureWrapS, (int)GLES.GLEnum.ClampToEdge);
            gl.TexParameter(GLES.GLEnum.Texture2D, GLES.GLEnum.TextureWrapT, (int)GLES.GLEnum.ClampToEdge);
            gl.TexParameter(GLES.GLEnum.Texture2D, GLES.GLEnum.TextureMinFilter, (int)GLES.GLEnum.Linear);
            gl.TexParameter(GLES.GLEnum.Texture2D, GLES.GLEnum.TextureMagFilter, (int)GLES.GLEnum.Linear);
            gl.BindTexture(GLES.GLEnum.Texture2D, (int)GLES.GLEnum.None);

            float[] verticesCoords = {
                -1.0f,  0.78f, 0.0f,  // Position 0
                -1.0f, -0.78f, 0.0f,  // Position 1
                1.0f,  -0.78f, 0.0f,  // Position 2
                1.0f,   0.78f, 0.0f,  // Position 3
            };

            float[] textureCoords = {
                0.0f,  0.0f,        // TexCoord 0
                0.0f,  1.0f,        // TexCoord 1
                1.0f,  1.0f,        // TexCoord 2
                1.0f,  0.0f         // TexCoord 3
            };
            ushort[] indices = { 0, 1, 2, 0, 2, 3 };
            gl.UseProgram(shaderProgram);

            fixed (void* prt = verticesCoords)
                gl.VertexAttribPointer(0, 3, GLES.GLEnum.Float, false, 12u, prt);

            fixed (void* prt = textureCoords)
                gl.VertexAttribPointer(1, 2, GLES.GLEnum.Float, false, 8u, prt);

            gl.EnableVertexAttribArray(0);
            gl.EnableVertexAttribArray(1);

            // Bind the Y plane map
            gl.ActiveTexture(GLES.GLEnum.Texture0);
            gl.BindTexture(GLES.GLEnum.Texture2D, yTextureId);
            // Set the Y plane sampler to texture unit to 0
            gl.Uniform1(ySamplerLoc, 0);

            // Bind the UV plane map
            gl.ActiveTexture(GLES.GLEnum.Texture1);
            gl.BindTexture(GLES.GLEnum.Texture2D, uvTextureId);
            // Set the UV plane sampler to texture unit to 0
            gl.Uniform1(uvSamplerLoc, 0);

            fixed (void* prt = indices)
                gl.DrawElements(GLES.GLEnum.Triangles, 6, GLES.GLEnum.UnsignedShort, prt);
        }


        private void OnUpdate(double ptr)
        {

        }

        private void OnRender(double ptr)
        {

        }

        #region static
        public static void InitGLES(GLES.GL gl)
        {
            string shader = @"
attribute vec4 vertexIn;
attribute vec2 textureIn;
varying vec2 textureOut;
void main(void)
{
    gl_Position = vertexIn;
    textureOut = textureIn;
}";
            //uint shaderPtr = gl.CreateShader(GLES.GLEnum.ShaderStorageBuffer);
            //gl.ShaderSource(shaderPtr, shader);
            //uint textureY = gl.GenTexture();
            //uint textureU = gl.GenTexture();
            //uint textureV = gl.GenTexture();
            //gl.TexParameter(GLES.GLEnum.Texture2D, GLES.GLEnum.TextureWrapS, (int)GLES.GLEnum.ClampToEdge);
        }

        #endregion
    }

    public class SilkOpenGLESUtilsExtensions
    {
        public static Silk.NET.Windowing.IWindow CreateNativeWin(int width, int height, string title = null)
        {
            WindowOptions options = new WindowOptions
            (
               true,
               new Vector2D<int>(50, 50),
               new Vector2D<int>(width, height),
               25d,
               25d,
               new GraphicsAPI
               (
                   ContextAPI.OpenGL,
                   ContextProfile.Core,
                   ContextFlags.ForwardCompatible,
                   new APIVersion(3, 3)
               ),
               title,
               Silk.NET.Windowing.WindowState.Normal,
               WindowBorder.Resizable,
               false,
               false,
               VideoMode.Default
            );
            return Silk.NET.Windowing.Window.Create(options);
        }

    }
}